package com.example.springframeworklearning.aspect.introductions;

import com.example.springframeworklearning.service.LogService;
import com.example.springframeworklearning.service.impl.LogServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;

/**
 * introduction 可以 使得 aspect 为 advice 实现指定的接口
 * https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introductions
 *
 * @author luo
 * @since 2022/9/23 08:45
 */
@Aspect
@Component
@Slf4j
public class LogIntroductionAspect {

    /**
     * introduction 使 aspect 能够让 advice 实现某一个接口
     * 并且为这些对象（advice）提供某一接口的实现
     * 例如：这里为 aspect class 内部的每一个 advice 都实现了 LogService，
     * advice 中具体对于 LogService 的实现类是 LogServiceImpl
     * ⚠️ 如果按照官网上的 将  @DeclareParents 的 value (AspectJ type pattern)中的末尾指定 + 号，则spring boot 无法正常启动
     * ⚠️ @DeclareParents 的 value 最终是要绑定到 接口实现类上 ，而不是接口本身
     * 本属性  nothing 无实际意义
     */
    @DeclareParents(value = "com.example.springframeworklearning.service..*", defaultImpl = LogServiceImpl.class)
    private static LogService nothing;

    /**
     * 绑定前置 advice
     * ⚠️ 这里只能 使用 this，而不能使用 target
     * 因为 this 指代的是 jdk 动态代理生成的 实现类
     * target 指代的是 手动编写的 实现类
     * ⚠️ @DeclareParents 是 为 advice 指定实现接口，而不是为 join point 方法所在的 class 实现新的接口
     * pointCut 既可以指定接口，也可以指定实现类
     * 但是无论绑定的是接口还是实现类，也无论使用的是 jdk 代理还是 CGLib 代理 @EnableAspectJAutoProxy(proxyTargetClass=true)
     * 实现类中在接口中不存在的方法是不会被绑定 advice 的
     *
     * @param logService 传入 实现了 LogService 的 advice
     */
    @Before(value = "execution(* com.example.springframeworklearning.service.impl.Order*.*(..))&&this(logService)", argNames = "logService")
    public void logAdvice(LogService logService) {
        log.info("我是 before advice");
        logService.record();
    }
}
